// Copyright (C) 2010 David Sugar, Tycho Softworks.
//
// This file is part of GNU uCommon C++.
//
// GNU uCommon C++ is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// GNU uCommon C++ is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GNU uCommon C++.  If not, see <http://www.gnu.org/licenses/>.


#include <apennine-config.h>
#include <ucommon/ucommon.h>
#include <ucommon/secure.h>
#include <ucommon/export.h>
#include <apennine/namespace.h>
#include <apennine/xmlrpc.h>
#include <ctype.h>

namespace apennine {

typedef struct
{
    const char *type;
    const char *value;
}   node_t;

static char *parseText(MemoryProtocol *alloc, CharacterProtocol& cp, int ch = 0)
{
    char text[1024];

    char *dp = text;
    char *ep = text + sizeof(text) - 2;
    char *amp = NULL;

    if(ch && ch != EOF)
        *(dp++) = (unsigned char)ch;

    while(dp < ep && EOF != (ch = cp.getchar())) {
        if(ch == '<') {
            *dp = 0;
            return alloc->dup(text);
        }

        if(ch == '&')
            amp = dp;
        *(dp++) = (unsigned char) ch;
        if(!amp)
            continue;

        if(eq(amp, "&amp;", 5)) {
            dp = ++amp;
            amp = NULL;
            continue;
        }

        if(eq(amp, "&gt;", 4)) {
            *amp = '>';
            dp = ++amp;
            amp = NULL;
            continue;
        }

        if(eq(amp, "&lt;", 4)) {
            *amp = '<';
            dp = ++amp;
            amp = NULL;
            continue;
        }

        if(eq(amp, "&quot;", 6)) {
            *amp = '\"';
            dp = ++amp;
            amp = NULL;
            continue;
        }

        if(eq(amp, "&apos;", 6)) {
            *amp = '\'';
            dp = ++amp;
            amp = NULL;
            continue;
        }
    }
    return NULL;
}

static int getspc(CharacterProtocol& cp)
{
    int ch;

    for(;;) {
        ch = cp.getchar();
        if(isspace(ch))
            continue;
        else
            return ch;
    }
}

static char *getxml(MemoryProtocol *alloc, CharacterProtocol& cp)
{
    int ch;
    char text[128];
    char *dp = text;
    char *ep = text + sizeof(text) - 2;
    bool comment = false;

    text[0] = '<';
    ++dp;

    while(dp < ep && EOF != (ch = cp.getchar())) {
        *(dp++) = (unsigned char)ch;
        if(ch == '>') {
            *dp = 0;
            return alloc->dup(text);
        }

        // we simply strip comment fields...
        if(ch == '?' && dp == text + 2) {
            comment = true;
            continue;
        }

        if(ch == '/' && dp == text + 2)
            continue;

        if(comment)
            --dp;

        if(!isalnum(ch))
            return NULL;
    }
    return NULL;
}

// returns value if available, and updates parser position...
static const char *parseValue(MemoryProtocol *alloc, CharacterProtocol& cp)
{
    const char *value = NULL;
    bool base64 = false;
    int ch;
    const char *xml;

    for(;;) {
        ch = getspc(cp);
        if(ch == EOF)
            return NULL;
        if(ch == '<') {
            xml = getxml(alloc, cp);
            if(!xml)
                return NULL;

            if(eq(xml, "<base64>"))
                base64 = true;
            else if(eq(xml, "<struct>"))
                return NULL;
            continue;
        }

        value = parseText(alloc, cp, ch);
        getxml(alloc, cp);    // extract /closure
        return value;
    }
    return NULL;
}

static node_t *parseNode(MemoryProtocol *alloc, CharacterProtocol& cp)
{
    node_t *node = NULL;
    int ch;
    const char *xml;

    while(EOF != (ch = getspc(cp))) {
        if(ch == '<') {
            xml = getxml(alloc, cp);
            if(!xml)
                break;
            if(eq(xml, "<struct>"))
                return NULL;

            if(eq(xml, "<array>"))
                return NULL;

            continue;
        }

        node = (node_t *)alloc->zalloc(sizeof(node_t));
        node->value = parseText(alloc, cp, ch);
        node->type = getxml(alloc, cp);

        if(!node->value || !node->type)
            return NULL;

        if(eq(node->type, "</int>"))
            node->type = "int";
        else if(eq(node->type, "</i4>"))
            node->type = "int";
        else if(eq(node->type, "</double>"))
            node->type = "double";
        else if(eq(node->type, "</string>"))
            node->type = "string";
        else if(eq(node->type, "</dateTime.iso8601>"))
            node->type = "dateTime.iso8601";
        else if(eq(node->type, "<base64>"))
            node->type = "base64";
        else
            return NULL;

        return node;
    }
    return NULL;
}

static const char *parseName(MemoryProtocol *alloc, CharacterProtocol& cp)
{
    const char *value = NULL;

    int ch = getspc(cp);
    const char *xml;

    if(ch == '<') {
        getxml(alloc, cp);
        ch = getspc(cp);
    }

    if(ch == EOF)
        return NULL;

    value = parseText(alloc, cp, ch);
    xml = getxml(alloc, cp);
    if(!xml)
        return NULL;

    if(eq(xml, "</name>"))
        return value;
    if(eq(xml, "</methodName>"))
        return value;

    return NULL;
}

bool XMLRPC::request(CharacterProtocol& cp)
{
    bool name_flag = false;
    const char *value;
    const char *map = NULL;
    const char *xml;
    bool callflag = false;

    method = NULL;

    while('<' == getspc(cp)) {
        xml = getxml(this, cp);
        if(!xml)
            return false;

        if(eq(xml, "<methodName>")) {
            if(callflag)
                method = parseName(this, cp);
            continue;
        }

        if(eq(xml, "<methodCall>")) {
            callflag = true;
            continue;
        }

        if(eq(xml, "<params>"))
            break;

        if(eq(xml, "<?>"))
            continue;
        return false;
    }

    if(!method)
        return false;

    argc = 0;
    unsigned index = 0;
    param *p = create();

    while('<' == getspc(cp)) {
        xml = getxml(this, cp);

        if(!xml)
            return false;

        if(eq(xml, "<name>")) {
            name_flag = true;
            p->name = parseName(this, cp);
            p->map = map;
            if(!p->name)
                return false;

            continue;
        }

        if(eq(xml, "</struct>") && map && !name_flag) {
            map = NULL;
            continue;
        }

        if(eq(xml, "<param>")) {
            p->name = p->value = p->map = NULL;
            p->arg = ++argc;
            continue;
        }

        if(eq(xml, "<value>")) {
            p->arg = argc;
            value = parseValue(this, cp);
            if(value) {
                p->value = value;
                add(p);
                p = create();
                p->arg = argc;
            }
            else if(name_flag)
                map = p->name;
            name_flag = false;
            continue;
        }

        if(eq(xml, "</params>")) {
            count = index;
            return true;
        }
    }
    return false;
}

bool XMLRPC::response(CharacterProtocol& cp)
{

    bool array_flag = false;
    bool name_flag = false;
    const char *map = NULL;
    unsigned params = 0;
    node_t *node;
    unsigned index = 0;
    const char *xml;
    bool rflag = false;

    failed = false;

    while('<' == getspc(cp)) {
        xml = getxml(this, cp);
        if(!xml)
            return false;

        if(eq(xml, "<params>")) {
            failed = false;
            break;
        }

        if(eq(xml, "<fault>")) {
            failed = true;
            break;
        }

        if(eq(xml, "<methodResponse>", 16)) {
            rflag = true;
            continue;
        }

        if(eq(xml, "<?>"))
            continue;

        return false;
    }

    if(!rflag)
        return false;

    argc = 0;
    param *p = create();

    while('<' == getspc(cp)) {
        xml = getxml(this, cp);
        if(!xml)
            return false;

        if(eq(xml, "</methodResponse>")) {
            count = index;
            if(index)
                argc = 1;
            return true;
        }

        if(eq(xml, "<param>")) {
            if(++params > 1)
                return false;
            continue;
        }

        if(eq(xml, "<data>")) {
            array_flag = true;
            continue;
        }

        if(eq(xml, "</data>")) {
            array_flag = false;
            continue;
        }

        if(eq(xml, "<name>")) {
            name_flag = true;
            p->name = parseName(this, cp);
            p->map = map;
            if(!p->name)
                return false;
            continue;
        }

        if(eq(xml, "</struct>") && map && !name_flag) {
            map = NULL;
            continue;
        }

        if(eq(xml, "<value>")) {
            node = parseNode(this, cp);
            if(node) {
                p->arg = 1;
                p->type = node->type;
                p->value = node->value;
                add(p);
                p = create();
            }
            else if(name_flag)
                map = p->name;
            name_flag = false;
            p->name = p->map = NULL;
            continue;
        }
    }
    return false;
}

xmlrpc *xmlrpc::response(CharacterProtocol& cp, size_t ps)
{
    xmlrpc *rpc = new xmlrpc(ps);
    if(rpc->XMLRPC::response(cp))
        return rpc;

    delete rpc;
    return NULL;
}

xmlrpc *xmlrpc::request(CharacterProtocol& cp, size_t ps)
{
    xmlrpc *rpc = new xmlrpc(ps);
    if(rpc->XMLRPC::request(cp))
        return rpc;

    delete rpc;
    return NULL;
}

xmlrpc *xmlrpc::create(size_t ps)
{
    return new xmlrpc(ps);
}

} // end namespace
